package com.fluidops.datacatalog.reltables.labelbase;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;

import org.apache.http.HttpResponse;
import org.apache.http.ParseException;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.util.EntityUtils;
import org.apache.log4j.Logger;

import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import org.json.simple.parser.JSONParser;

import com.fluidops.datacatalog.reltables.util.LRUCache;

public class Freebase implements ILabelSource{

	private static Logger logger = Logger.getLogger(Freebase.class.getName());

	public static final int INTEGER_IDENTIFIER = 1;
	
	public static final String FREEBASE_SERVICE_URL = "http://api.freebase.com/api/service/search?query=";

	public static final int MAX_CACHE_SIZE = 250000;

	private int numLabels = 10;

	private int numSearchResults  = 2;

	private HttpClient client = new DefaultHttpClient(); 

	private Map<String, Set<String>> labelCache;

	private JSONParser jsonParser = new JSONParser();


	public Freebase(){
		this.labelCache = new LRUCache<String, Set<String>>(MAX_CACHE_SIZE);
	}

	public Freebase(int numLabels, int numSearchResults){
		this();
		this.numLabels = numLabels;
		this.numSearchResults = numSearchResults;
	}

	@Override
	public Set<String> getLabel(Set<String> keywords){
		Set<String> result = new TreeSet<String>();
		for(String keyword : keywords){
			result.addAll(getLabel(keyword));
		}
		return result;
	}


	@Override
	public Set<String> getLabel(String keyword) {
		if(!this.labelCache.containsKey(keyword)){
			String url = getQuery(keyword);
			/*
					.replace(" ", "+");
			url = url.replace("%", "");
			*/
			HttpResponse response = null;
			try {
				response = this.client.execute(new HttpGet(url));
			} catch (ClientProtocolException e) {
				e.printStackTrace();
			} catch (IOException e) {
				e.printStackTrace();
			}
			Set<String> labels = parseResult(response);
			this.labelCache.put(keyword, labels);

		}

		return this.labelCache.get(keyword);
	}

	private String getQuery(String keyword){
		StringBuilder query = new StringBuilder(FREEBASE_SERVICE_URL);
		try {
			query.append(URLEncoder.encode(keyword, "UTF-8"));
		} catch (UnsupportedEncodingException e) {
			logger.warn("Could not encode freebase url ", e);
		}
		query.append("&limit=").append(this.numSearchResults);
		return query.toString();
	}


	private Set<String> parseResult(HttpResponse response){
		Set<String> labels = new TreeSet<String>();
		/*
		 *  
		 * result: [
					0: {
					name: "European Union"
					search: {
							query: "eu 25"
							score: 66.223206
					type: [
					0: {
						name: "Topic"
		 * 
		 */
		if(response != null){
			try {
				String entityString = EntityUtils.toString(response.getEntity());
				JSONObject resObj = (JSONObject) this.jsonParser.parse(entityString);
				// get all results 
				JSONArray results = (JSONArray) resObj.get("result");
				if(results != null){
					JSONObject singleResult, jsonLabel;
					JSONArray jsonLabelArray;

					int numCurrentResults;
					for(Object kwdRes : results){
						numCurrentResults = 0;
						// we have one result of the keyword query
						singleResult = (JSONObject) kwdRes;
						// get all the types that belong to the single keyword result
						jsonLabelArray = (JSONArray) singleResult.get("type");
						//store every single type to the result list
						if(jsonLabelArray != null){
							for(Object objectLabel : jsonLabelArray){
								if(numCurrentResults < this.numLabels){
									jsonLabel = (JSONObject) objectLabel;
									// finally we have the label of the type
									String name = jsonLabel.get("name").toString();
									if(name != null){
										labels.add(name);
										numCurrentResults++;
									}
								}
							}
						}

					}
				}else{
					logger.warn("Could not retrieve results from Freebase " + entityString);
				}
			} catch (ParseException e) {
				logger.warn("Parser Exception while parsing HttpResponse. ", e);
			} catch (org.json.simple.parser.ParseException e) {
				logger.warn("Parser Exception while parsing HttpResponse. ", e);
			} catch (IOException e) {
				logger.warn("IOException while parsing HttpResponse. ", e);
			}
		}
		return labels;
	}

	@SuppressWarnings("unchecked")
	public void loadFromFile(File f){
		try {
			FileInputStream fis = new FileInputStream(f);
			ObjectInputStream ois = new ObjectInputStream(fis);
			this.labelCache = (LRUCache<String, Set<String>> ) ois.readObject();


			ois.close();
			fis.close();
		} catch (FileNotFoundException e) {
			logger.info("Could not find a backup of the cache starting with a empty cache. ");
			this.labelCache = new LRUCache<String, Set<String>>(MAX_CACHE_SIZE);
		} catch (IOException e) {
			logger.info("Could not find a backup of the cache starting with a empty cache. ");
			this.labelCache = new LRUCache<String, Set<String>>(MAX_CACHE_SIZE);
		} catch (ClassNotFoundException e) {
			logger.warn("Class not found while trying to restore labelCache", e);
			this.labelCache = new LRUCache<String, Set<String>>(MAX_CACHE_SIZE);
		}
	}

	public void saveToFile(File f){
		try {
			
			f.delete();
			f.createNewFile();
			
			FileOutputStream fos = new FileOutputStream(f);
			ObjectOutputStream oos = new ObjectOutputStream(fos);
			oos.writeObject(this.labelCache);

			oos.close();
			fos.close();
		} catch (FileNotFoundException e) {
			logger.info("Could not find a backup of the cache starting with a empty cache. ");
		} catch (IOException e) {
			logger.info("Could not find a backup of the cache starting with a empty cache. ");
		} 
	}

	@Override
	public int getIntegerIdentifier() {
		return INTEGER_IDENTIFIER;
	}
	
	@Override
	public boolean equals(Object o){
		if(o instanceof ILabelSource){
			ILabelSource ls = (ILabelSource) o;
			return this.getIntegerIdentifier() == ls.getIntegerIdentifier();
		}
		return false;
	}
}
